<?php
declare(strict_types=1);

namespace App\Utils\Other\Payment\Chinapnrpay;

use App\Exception\InternalException;
use App\Utils\Http\Request;
use App\Utils\Other\Payment\Base;
use Hyperf\Collection\Arr;
use function Hyperf\Support\make;
use function Hyperf\Translation\__;

/**
 * Class App
 * @package App\Utils\Other\Payment\Sumpay
 */
abstract Class App extends Base {

    /**
     * 组装参数
     * @param array $params
     * @return array
     * @throws \Psr\Container\ContainerExceptionInterface
     * @throws \Psr\Container\NotFoundExceptionInterface
     */
    protected function params(array $params = []): array
    {
        $body = [
            'version' => '3.0',
            'signType' => '4',
            'merchantAcctId' => $this->getConfig('mer_no'),
            'customerIp' => getIp(),
            'orderCurrency' => 'CNY',
            'settlementCurrency' => 'CNY'
        ];

        $body = array_merge($body, $params);
        $body['signMsg'] = $this->sign($body);

        return $body;
    }

    /**
     * 发起请求
     * @param array $params
     * @return array
     */
    protected function request(array $params = []): array
    {
        // TODO: Implement request() method.
        /** @var Request $request */
        $request = make(Request::class);

        $params = $this->params($params);
        $response = $request->post($this->getConfig('url').$this->getConfig('path'), $params);

        return $this->response($response, $params);
    }

    /**
     * 处理响应结果
     * @param $response
     * @param array $params
     * @return array
     */
    protected function response($response, array $params = []): array
    {
        logs($response);
        if (!$response) {
            throw new InternalException(__('request.nobody'));
        }

        parse_str(urldecode($response), $result);

        if (!$result) {
            throw new InternalException(__('request.params_error'));
        }

        if (Arr::get($result, 'errCode') !== '000000' && Arr::get($result, 'respCode') !== '000000') {
            $code = $result['errCode']??$result['respCode'];
            throw new InternalException(__('request.error', [
                'code' => $code,
                'message' => $result['respMsg']??$result['errMsg']??$code
            ]));
        }

        return [$params, $result, $result];
    }

    /**
     * 签名算法
     * @param array $params
     * @return string
     */
    protected function sign(array $params): string
    {
        $params = array_filter($params, fn ($v) => $v || is_numeric($v));
        $body = [];
        foreach ($this->getConfig('params_sort') as $key) {
            if (Arr::has($params, $key)) {
                $body[$key] = $params[$key];
            }
        }

        $signStr = urldecode(htmlspecialchars_decode(http_build_query($body)));
        logs($signStr);

        $fp = fopen($this->getConfig('private_cert_path'), 'r');
        $privateKey = fread($fp, filesize($this->getConfig('private_cert_path')));
        fclose($fp);
        $pkeyId = openssl_get_privatekey($privateKey);
        openssl_sign($signStr, $signature, $pkeyId, OPENSSL_ALGO_SHA1);

        return base64_encode($signature);
    }

    /**
     * 验证签名
     * @param array $params
     * @return bool
     */
    protected function check(array $params): bool
    {
        $mac = base64_decode(urldecode($params['signMsg']));

        $params = array_filter($params, fn ($v) => $v || is_numeric($v));
        $body = [];
        foreach ($this->getConfig('params_sort') as $key) {
            if (Arr::has($params, $key)) {
                $body[$key] = $params[$key];
            }
        }
        $signStr = urldecode(htmlspecialchars_decode(http_build_query($body)));
        logs($signStr);

        $fp = fopen($this->getConfig('public_cert_path'), 'r');
        $cert = fread($fp, filesize($this->getConfig('public_cert_path')));
        fclose($fp);
        $pubKeyId = openssl_get_publickey($cert);
        if (false === openssl_verify($signStr, $mac, $pubKeyId)) {
            throw new InternalException(__('message.sign.failed'));
        }

        return true;
    }
}